/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
/*
 * Created on 07.04.2005
 * 
 * TODO To change the template for this generated file go to Window -
 * Preferences - Java - Code Style - Code Templates
 */package robocup.component.worldmodel;

import java.util.Arrays;
import java.util.EnumMap;

import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.geometry.Vektor;
import robocup.component.infotypes.PlayModeInfo;
import robocup.component.infotypes.PlayerSayInfo;
import robocup.component.infotypes.RefereeInfo;
import robocup.component.infotypes.SenseBodyInfo;
import robocup.component.infotypes.VisualInfo;
import robocup.component.math.RCMath;
import robocup.component.tactics.AbstractState.STATES;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.DynamicObject;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.UPDATE_MODE;

/**
 * This class remembers the seen field areas. will be used to calculate the
 * angle to turn the neck to.
 * 
 * @author rw, gamel
 */
public class NeckRotator implements IModel {

	/**
	 * The statistics for calling actions(states).
	 * 
	 * @author rw
	 */
	private class Statistics {

		// map containing booleans
		private EnumMap<STATES, Integer> actionCounter = new EnumMap<STATES, Integer>(
				STATES.class);

		private int counter = 0;

		/**
		 * creates new statistics
		 */
		public Statistics() {

			reset();
		}

		private void reset() {

			for (STATES a : STATES.values())
				this.actionCounter.put(a, new Integer(0));
			this.counter = 0;
		}

		/**
		 * @return the counter
		 */
		public int getCounter() {

			return this.counter;
		}

		private Integer getCounter(STATES a) {

			return this.actionCounter.get(a);
		}

		/**
     * increases the counter for s
		 * @param s the state to count
		 */
		public void update(STATES s) {

			this.actionCounter.put(s, getCounter(s) + 1);
			this.counter++;
		}

		/**
		 * prints the statistics on screen
		 */
		public void print() {

			System.out.println("NeckRotatorStatistics:");
			for (STATES a : STATES.values())
				System.out.println(a.toString() + " #:" + getCounter(a) + " %:"
						+ ((double) getCounter(a).intValue() / this.counter));
		}

	}

	// neckRotator statistics
	private Statistics statistics = new Statistics();

	@SuppressWarnings("unused")
	private SConf sConf = SConf.getInstance();

	private WorldModel world;

	Ball dummyBall = Ball.getDummyInstance();

	Player dummyPlayer = Player.getDummyInstance();

	Vektor dummyVektor1 = Vektor.getDummyInstance();

	Vektor dummyVektor2 = Vektor.getDummyInstance();

	Vektor objectPos = new Vektor();

	Vektor oppLeftPost = new Vektor(Vektor.XY, sConf.half_goal_width,
			sConf.half_length);

	Vektor oppRightPost = new Vektor(Vektor.XY, -sConf.half_goal_width,
			sConf.half_length);

	// the number of slices as exponent (number of slices is 2^numberSlicesExp)
	private int numberSlicesExp = 5; // 2 =

	// ....
	double[] preferredObjects = new double[23];

	boolean setPref = false;

	// records the last time seen that slice
	// it's possible that the player sees only parts of the slice -> double
	private double[] sliceHistory = new double[(int) Math.pow(2,
			this.numberSlicesExp)];

	// the area of a slice
	private double[] estimatedSliceAreas = new double[(int) Math.pow(2,
			this.numberSlicesExp)];

	// the modifiing value for important objects
	private double[] importantObjectValue = new double[(int) Math.pow(2,
			numberSlicesExp)];

	// seeable slices set to 1, slices in the players back set to 0
	private int[] seeableSlices = new int[(int) Math.pow(2,
			this.numberSlicesExp)];

	// gives the importance of a slice belonging to the actual state of the
	// player
	// should be between 0(unimportant) - 1(important)
	private double[] stateSlices = new double[(int) Math.pow(2,
			this.numberSlicesExp)];

	// just for good debugging
	private double[] finalSlices = new double[(int) Math.pow(2,
			this.numberSlicesExp)];

	/*
	 * weights set the relation between state, history and area, what connected
	 * gives the final value of a slice the weights should add up to 1
	 */
	private double stateWeight = 3.0;

	private double historyWeight = 0.5;

	private double areaWeight = 0.5;

	/*
	 * here the maxima of each array are collected, to normalize the arrays
	 * between 0 - 1
	 */
	private double stateMax = 0.;

	private double areaMax = 0.;

	// last pos used for calculating the slice areas
	private Vektor lastPos = new Vektor(Vektor.XY, 1000, 1000);

	// start calculating the areas only when the position is different
	private double minPosDiff = 5.0;

	// biggest distance between two field points
	private double maxDistInField = 1000;

	// that's the state of the last cycle
	private STATES lastState = null;

	// that's the state of this cycle
	private STATES thisState = null;

	// number of searchBalls without other state between it
	private int searchCount = 0;

	/**
	 * Constructor - a neckrotator can calculate a good view-direction in
	 * depenedence on the last seen directions and the actual state.
	 * 
	 * @param world -
	 *            the current world-model (parent)
	 */
	public NeckRotator(WorldModel world) {

		this.world = world;
		// set each slice to "not seen since 10 cylces"
		Arrays.fill(this.sliceHistory, 10.0);
		// all slices have the same size
		Arrays.fill(this.estimatedSliceAreas, 1);
		// state values aren't already set
		Arrays.fill(this.seeableSlices, 1);
		// state values aren't already set
		Arrays.fill(this.stateSlices, 1);
		// final values will be calculated later
		Arrays.fill(finalSlices, 1);
		// initially there are no important objects
		Arrays.fill(importantObjectValue, 0);
		maxDistInField = 2 * Math.sqrt(sConf.half_length * sConf.half_length
				+ sConf.half_width * sConf.half_width);

	}

	/**
	 * This method is called when an VI-Arrived and the World has been updated
	 * Set's the seen slices.
	 * @param info the visual info
	 * 
	 */
	public void update(VisualInfo info) {

		updateSeeHistory(this.world.getHeadAngle(), this.world.getViewWidth());
		setEstimatedSliceAreaValues();
		setPref = false;
	}

	/**
	 * This method should be called when BS-Arrived.
	 * @param info the sense body info
	 * 
	 */
	public void update(SenseBodyInfo info) {

		for (int i = 0; i < this.sliceHistory.length; i++) {
			this.sliceHistory[i] += 1;
		}
		setPref = false;
	}

	/**
	 * 
	 * @param hist -
	 *            a history value
	 * @return an value, that stands for the importance of the goven history
	 */
	private double roundHistory(double hist) {

		if (hist < 1) {
			return 0.1;
		}
		if (hist < 2)
			return 1.5;
		if (hist < 3)
			return 2.5;
		if (hist < 4)
			return 3.5;
		if (hist < 5)
			return 4.0;
		if (hist < 6)
			return 4.5;
		if (hist < 8)
			return 5.;
		if (hist < 10)
			return 6.;
		if (hist < 25)
			return 7.;
		if (hist < 50)
			return 8.;
		if (hist < 100)
			return 9.;
		return 10.;

	}

	/**
	 * The width of a slice.
	 * 
	 * @return width of a slice
	 */
	public double getSliceWidth() {

		return (360 / Math.pow(2, this.numberSlicesExp));
	}

	/**
	 * The number of slices.
	 * 
	 * @return number of slices
	 */
	private int getNumberSlices() {

		return (int) Math.pow(2, this.numberSlicesExp);
	}

	/**
	 * The slice-index containing a point.
	 * 
	 * @param pos
	 * @param point
	 * @return slice-index containing a point
	 */
	private int getSliceContainingPoint(Vektor pos, Vektor point) {

		double angle = pos.getAngleTo(point);
		int value = (int) (angle / getSliceWidth()) + getNumberSlices() / 2 - 1;
		if (value < 0)
			value += getNumberSlices();
		return value;
	}

	/**
	 * The slice-index containing a abs. angle.
	 * @param angle the desired angle
	 * 
	 * @return slice-index containing a abs. angle
	 */
	public int getSliceContainingAbsAngle(double angle) {

		return (int) (RCMath.getPositiveNormalizedAngle(angle - 180) / 360.0 * this
				.getNumberSlices());
	}

	/**
	 * The angle of the left side of an slice. (seen from the center)
	 * 
	 * @param slice -
	 *            the slice (array index)
	 * @return normalized angle in degree
	 */
	private double getSliceLeftAngle(int slice) {

		if (slice < 0 || slice >= getNumberSlices())
			throw new IndexOutOfBoundsException();
		return (-180.0 + 360.0 * slice / getNumberSlices());
	}

	/**
	 * The angle of the right side of an slice. (seen from the center)
	 * 
	 * @param slice -
	 *            the slice (array index)
	 * @return normalized angle in degree
	 */
	private double getSliceRightAngle(int slice) {

		if (slice < 0 || slice >= getNumberSlices())
			throw new IndexOutOfBoundsException();
		return (-180.0 + 360.0 * (slice + 1) / getNumberSlices());
	}

	/**
	 * the angle representing the middle between left and right slice side.
	 * 
	 * @param slice
	 * @return the meridian (angle between left and right slice margins)
	 */
	private double getSliceMeridian(int slice) {

		return ((getSliceLeftAngle(slice) + getSliceRightAngle(slice)) / 2.0);
	}

	private int getLeftSliceNeighbour(int slice) {

		int neighbour = slice - 1;
		if (neighbour < 0) {
			neighbour += getNumberSlices();
		}
		return neighbour;
	}

	private int getRightSliceNeighbour(int slice) {

		int neighbour = slice + 1;
		if (neighbour >= getNumberSlices()) {
			neighbour -= getNumberSlices();
		}
		return neighbour;
	}

	/**
	 * The % of the slice area that has been seen
	 * 
	 * @param slice -
	 *            the slice (array index)
	 * @param left -
	 *            left view cone border
	 * @param right -
	 *            right view cone border
	 * @return the percentage of seen area of the slice
	 */
	private double overlapAreaPercentage(int slice, double left, double right) {

		double sliceArea = 360 / Math.pow(2, this.numberSlicesExp);
		double sliceLeft = getSliceLeftAngle(slice);
		double sliceRight = getSliceRightAngle(slice);
		// angle crossed the +-180 border
		if (left > right) {
			left = Vektor.normalize(left + 180);
			right = Vektor.normalize(right + 180);
			sliceLeft = Vektor.normalize(sliceLeft + 180);
			sliceRight = Vektor.normalize(sliceRight + 180);
		}
		// no overlap
		if (sliceRight < left || sliceLeft > right) {
			return 0;
		}
		// full overlap (slice in angle)
		if (left <= sliceLeft && right >= sliceRight) {
			return 1;
		}
		// angle inside slice
		if (sliceLeft <= left && sliceRight >= right) {
			return (right - left) / sliceArea;
		}
		// parts of the slice overlap
		double overlap = Math.min(right - sliceLeft, sliceRight - left);
		if (overlap / sliceArea > 1)
			System.out.println("NeckRotator: OVERLAP > 1 ="
					+ (overlap / sliceArea) + "SA: " + sliceArea + " l" + left
					+ ":r " + right + ":sL " + sliceLeft + ":sR " + sliceRight);
		return (overlap / sliceArea);
	}

	/**
	 * Updates the see-history of the slices (when a slice was last seen).
	 * 
	 * @param headDir -
	 *            abs. headDir
	 * @param viewWidth -
	 *            view cone width
	 */
	private void updateSeeHistory(double headDir, double viewWidth) {

		double leftBorder = Vektor.normalize(headDir - viewWidth / 2.0);
		double rightBorder = Vektor.normalize(headDir + viewWidth / 2.0);
		double overlapPercentage;
		// history maximum will be calculated new
		for (int i = 0; i < this.sliceHistory.length; i++) {
			overlapPercentage = overlapAreaPercentage(i, leftBorder,
					rightBorder);
			// != 1.0 because every slice should be more interesting than
			// "unseeable" slices (value 0)
			this.sliceHistory[i] *= (1.000001 - overlapPercentage);
			if (this.thisState == STATES.SEARCH_BALL
					&& overlapPercentage >= 0.95) {
				this.stateSlices[i] *= 0.1;
			} else if (this.thisState == STATES.SEARCH_BALL) {
				this.stateSlices[i] += 1;
			}
		}
	}

	/**
	 * estimate the intersecting area of a slice and the field. (only the
	 * sliceArea to the next line in slice direction is taken - shouldn't
	 * matter)
	 * 
	 * not original area values are calculated, but a value with the same
	 * growth-rate --> same effect
	 * 
	 */
	private void setEstimatedSliceAreaValues() {

		Vektor pos = this.world.getMyPosition();
		// no major changes to expect
		if (pos.getDistance(this.lastPos) < this.minPosDiff
				&& this.world.getCycle() > 0) {
			return;
		}
		// maximum will be new calculated now
		this.areaMax = 0;

		// reset
		this.lastPos = pos;
		double maxArea = this.maxDistInField * this.maxDistInField;
		Arrays.fill(this.estimatedSliceAreas, maxArea);
		double distToTop = this.sConf.half_length - pos.y;
		double distToBottom = this.sConf.half_length + pos.y;
		double distToRight = this.sConf.half_width - pos.x;
		double distToLeft = this.sConf.half_width + pos.x;

		// when outside field set to border
		distToTop = Math.max(0, distToTop);
		distToBottom = Math.max(0, distToBottom);
		distToLeft = Math.max(0, distToLeft);
		distToRight = Math.max(0, distToRight);

		// the direction of the players nose
		double viewDir;
		// viewDir in radians
		double viewDirRad;
		// distance to Line in dir = viewDir
		double distToLine;
		double triangleArea;

		for (int slice = 0; slice < this.estimatedSliceAreas.length; slice++) {
			// set viewDir to the angle in the slice center
			viewDir = getSliceMeridian(slice);
			viewDirRad = Math.toRadians(viewDir);

			// to top (~0�)
			if (Math.abs(viewDir) < 90) {
				distToLine = distToTop / Math.cos(viewDirRad);
				triangleArea = distToLine * distToLine;
				this.estimatedSliceAreas[slice] = Math
						.min(this.estimatedSliceAreas[slice], Math
								.abs(triangleArea));
			}
			// to bottom (~+-180�)
			if (Math.abs(viewDir) > 90) {
				distToLine = distToBottom / Math.cos(viewDirRad + Math.PI);
				triangleArea = distToLine * distToLine;
				this.estimatedSliceAreas[slice] = Math
						.min(this.estimatedSliceAreas[slice], Math
								.abs(triangleArea));
			}
			// to right (~90�)
			if (viewDir > 0) {
				distToLine = distToRight / Math.cos(Math.PI / 2 - viewDirRad);
				triangleArea = distToLine * distToLine;
				this.estimatedSliceAreas[slice] = Math
						.min(this.estimatedSliceAreas[slice], Math
								.abs(triangleArea));
			}
			// to left (~-90�)
			if (viewDir < 0) {
				distToLine = distToLeft / Math.cos(-Math.PI / 2 - viewDirRad);
				triangleArea = distToLine * distToLine;
				this.estimatedSliceAreas[slice] = Math
						.min(this.estimatedSliceAreas[slice], Math
								.abs(triangleArea));
			}
			if (this.estimatedSliceAreas[slice] > maxArea) {
				System.out.println("Seems that a slice area is bigger then "
						+ "possible, correct Neckrotator");
			}
			// set possibly new maximum
			if (this.areaMax < this.estimatedSliceAreas[slice])
				this.areaMax = this.estimatedSliceAreas[slice];
		}
	}

	/**
	 * Slices in the back of the player that can't be seen are set to 0.
	 * 
	 * @param bodyDir -
	 *            abs body dir angle
	 * @param viewWidth -
	 *            view cone width
	 */
	public void setUnseeableSlices(double bodyDir, double viewWidth) {

		// margins for seeing something
		double leftMax = Vektor.normalize(bodyDir + this.sConf.MINNECKANG
				- viewWidth / 2);
		double rightMax = Vektor.normalize(bodyDir + this.sConf.MAXNECKANG
				+ viewWidth / 2);
		// margins skip the +-180 border (sign swaps)
		boolean skips180 = leftMax > rightMax;

		// number of relevant slices (slices that can be seen with given view
		// width)
		int slices = (int) (viewWidth / this.getSliceWidth());

		// at least one slice
		if (slices == 0) {
			System.out
					.println("0 Slices can be seen --> impossible, correct Neckrotator");
			slices++;
		}

		// middle of a slice
		double meridian = 0;

		// set unseeable to 0
		for (int i = 0; i < getNumberSlices(); i++) {
			meridian = getSliceMeridian(i);
			// no skipping
			if (!skips180 && (meridian >= leftMax) && (meridian <= rightMax)) {
				this.seeableSlices[i] = 1;
			}
			// skipping
			else if (skips180
					&& ((meridian > leftMax) || (meridian < rightMax))) {
				this.seeableSlices[i] = 1;
			}
			// not seen
			else {
				this.seeableSlices[i] = 0;
			}
			if ((Math.abs(this.sConf.MINNECKANG - viewWidth / 2)
					+ this.sConf.MAXNECKANG + viewWidth / 2) >= 360) {
				this.seeableSlices[i] = 1;
			}
		}
	}

	/**
	 * Sets a value for each slice that represents the probability that the
	 * slice contains the ball.
	 * 
	 * @param pos the players position
	 */
	public void setSearchBallValues(Vektor pos) {

		this.searchCount = 1;
		int ballSlice = getSliceContainingPoint(pos, this.world
				.getNextPosition(this.world.getBallRef()));
		double diff;
		// the ball prob of each slice depends of the slice's distance to the
		// slice
		// of the last ball
		for (int i = 0; i < this.stateSlices.length; i++) {
			diff = Math.abs(i - ballSlice);
			if (diff > this.stateSlices.length / 2)
				diff = this.stateSlices.length - diff;
			// exp. relation between distance and value
			this.stateSlices[i] = 10 / Math.pow(2, diff / 2);
			if (this.stateSlices[i] < 0.01) {
				this.stateSlices[i] = 0.01;
			}

		}
	}

	/**
	 * sets the slice-values depending on several weights and depending on the
	 * objects within this slice
	 * 
	 * @param pos -
	 *            position of the player
	 * @param ballWeight -
	 *            importance of the ball
	 * @param weight3m -
	 *            importance of other players who are within 3m around the
	 *            player should be a small value because these players are
	 *            already sensed with body
	 * @param weight10m -
	 *            importance of other players who are within 3 - 10m around the
	 *            player
	 * @param weight25m -
	 *            importance of other players who are within 10 - 25m around the
	 *            player
	 * @param weight40m -
	 *            importance of other players who are within 25 - 40m around the
	 *            player
	 * @param farAwayWeight -
	 *            importance of other players who are more than 40m away from
	 *            the player
	 * @param kickableTeam -
	 *            ball is more important, if it can be kicked by a teammate?
	 * @param kickableOpp -
	 *            ball is more important, if it can be kicked by an opponent?
	 * @param teamWeight -
	 *            how important are teamMates?
	 * @param oppWeight -
	 *            how important are opponent players?
	 * @param oppGoalieWeight -
	 *            maybe I should look to the opponent goalie sometimes?
	 * 
	 */

	public void setDefaultValues(Vektor pos, double ballWeight,
			double weight3m, double weight10m, double weight25m,
			double weight40m, double farAwayWeight, boolean kickableTeam,
			boolean kickableOpp, double teamWeight, double oppWeight,
			double oppGoalieWeight) {

		double dist2player;
		double dist2Ball;
		double dist2Goal;
		double posWeight = 1;
		double roleWeight = 1;
		Vektor ballPos = this.world.getNextPosition(this.world.getBallRef());
		boolean kickable = false;
		Player[] players = this.world.getExpectedPlayers();// (rw).getAllPlayers();

		// init slices
		for (int i = 0; i < this.stateSlices.length; i++) {
			this.stateSlices[i] = 0.1;
		}

		// set ball-values
		int ballSlice = getSliceContainingPoint(pos, ballPos);
		int leftSlice = getLeftSliceNeighbour(ballSlice);
		int rightSlice = getRightSliceNeighbour(ballSlice);
		double ballAge = world
				.getBallRef()
				.getAge(
						world.getBodyCycle(),
						robocup.component.worldobjects.Ball.UPDATE_MODE.SEEN_WITHOUT_SPEED) + 0.1;
		ballAge = Math.min(world.getBallRef().getAge(world.getBodyCycle(),
				robocup.component.worldobjects.Ball.UPDATE_MODE.HEARED) + 1,
				ballAge);

		if (preferredObjects[22] < 0) {
			ballWeight *= (1 + preferredObjects[22]);
		}
		stateMax = 0;
		if (!(world.getPlayMode() == PLAY_MODE.PLAY_ON)
				&& world.getBallRef().isCatched()
				&& world.getOpponentsGoalie() != null) {
			if (world.getOpponentsGoalie().getDistance(world.getBallRef()) < 10) {
				dummyVektor1.x = -sConf.half_penalty_width;
				dummyVektor1.y = sConf.half_length - sConf.penalty_length;
				dummyVektor2.x = sConf.half_penalty_width;
				dummyVektor2.y = sConf.half_length - sConf.penalty_length;
				int startslice = getSliceContainingPoint(pos, dummyVektor1);
				int endslice = getSliceContainingPoint(pos, dummyVektor2);
				// System.out.println("Opponent Goalie catched Ball: NR looks
				// slices "+startslice+" to "+endslice);
				for (int i = startslice; i <= endslice; i++) {
					stateSlices[i] += ballAge * ballWeight;
					if (stateMax < stateSlices[i])
						stateMax = stateSlices[i];
				}
			}
		} else {
			stateSlices[ballSlice] += ballAge * ballWeight;
			stateSlices[leftSlice] += ballAge * ballWeight / 2;
			stateSlices[rightSlice] += ballAge * ballWeight / 2;
			if (stateMax < stateSlices[ballSlice]) {
				stateMax = stateSlices[ballSlice];
			}
		}
		// set opponent goal values
		int goalSlice = getSliceContainingPoint(pos,
				SConf.getInstance().GOAL_POS_OTHER);
		leftSlice = getLeftSliceNeighbour(goalSlice);
		rightSlice = getRightSliceNeighbour(goalSlice);
		dist2Goal = pos.getDistance(SConf.getInstance().GOAL_POS_OTHER);
		if (dist2Goal < 20) {
			this.stateSlices[goalSlice] += 10;
			this.stateSlices[leftSlice] += 6;
			this.stateSlices[rightSlice] += 6;
		}
		if (this.stateMax < this.stateSlices[goalSlice])
			this.stateMax = this.stateSlices[goalSlice];
		if (this.stateMax < this.stateSlices[leftSlice])
			this.stateMax = this.stateSlices[leftSlice];
		if (this.stateMax < this.stateSlices[rightSlice])
			this.stateMax = this.stateSlices[rightSlice];

		for (int i = 0; i < players.length; i++) {

			// skip the self
			if (i == this.world.getNumber() - 1)
				continue;

			double vAge;
			if (i < 11)
				vAge = players[i].getAge(this.world.getBodyCycle(),
						UPDATE_MODE.SEEN_NOTEAM) + 0.1;
			else
				vAge = players[i].getAge(this.world.getBodyCycle(),
						UPDATE_MODE.META_BASED_ON_INFO) + 0.1;

			Vektor ppos = players[i].getPosition();
			dist2player = pos.getDistance(ppos);
			dist2Ball = ballPos.getDistance(ppos);
			if (!this.world.inField(ppos)) {
				posWeight = 0.0;
			} else if (players[i].isMe()) {
				posWeight = 0.0;
			} else if (dist2player < 3) {
				posWeight = weight3m;
			} else if (dist2player < 10) {
				posWeight = weight10m;
			} else if (dist2player < 25) {
				posWeight = weight25m;
			} else if (dist2player < 40) {
				posWeight = weight40m;
			} else {
				posWeight = farAwayWeight;
			}
			if (ppos.y > world.getMyPosition().y && players[i].isFriend()) {
				posWeight *= 4;
			} else if (ppos.y > world.getMyPosition().y - 10
					&& players[i].isFriend()) {
				posWeight *= 2;
			}
			if (preferredObjects[i] < 0) {
				posWeight *= (preferredObjects[i] + 1);
			}
			int pSlice = getSliceContainingPoint(pos, ppos);
			leftSlice = getLeftSliceNeighbour(pSlice);
			rightSlice = getRightSliceNeighbour(pSlice);
			if (players[i].isFriend()) {
				roleWeight = teamWeight;
				if (players[i].getKickDistance() >= dist2Ball && kickableTeam) {
					kickable = true;
				}
			} else {
				if (players[i].getKickDistance() >= dist2Ball && kickableOpp) {
					kickable = true;
				}
				if (players[i].isGoalie()) {
					roleWeight = oppGoalieWeight;
				} else {
					roleWeight = oppWeight;
				}
			}
			if (pSlice >= 0) {
				if (players[i].isReliable()) {
					this.stateSlices[pSlice] += (vAge * posWeight * roleWeight);
					this.stateSlices[leftSlice] += (vAge * posWeight
							* roleWeight / 2.);
					this.stateSlices[rightSlice] += (vAge * posWeight
							* roleWeight / 2.);
				} else {
					if (players[i].isFriend() || !(players[i].isGoalie())) {
						this.stateSlices[pSlice] += vAge * posWeight
								* roleWeight / 3.;
						this.stateSlices[leftSlice] += (vAge
								* posWeight * roleWeight / 6.);
						this.stateSlices[rightSlice] += (vAge
								* posWeight * roleWeight / 6.);
					} else {
						// it's the opponent goalie
						int lGoalSlice = getSliceContainingPoint(pos,
								oppLeftPost);
						int rGoalSlice = getSliceContainingPoint(pos,
								oppRightPost);
						int mGoalSlice = getSliceContainingPoint(pos,
								sConf.GOAL_POS_OTHER);
						this.stateSlices[lGoalSlice] += (vAge
								* posWeight * roleWeight);
						this.stateSlices[rGoalSlice] += (vAge
								* posWeight * roleWeight);
						this.stateSlices[mGoalSlice] += (vAge
								* posWeight * roleWeight);
					}
				}
				if (this.stateMax < this.stateSlices[pSlice])
					this.stateMax = this.stateSlices[pSlice];
				if (this.stateMax < this.stateSlices[leftSlice])
					this.stateMax = this.stateSlices[pSlice];
				if (this.stateMax < this.stateSlices[rightSlice])
					this.stateMax = this.stateSlices[pSlice];
			}
		}
		if (kickable && preferredObjects[22] > 0) {
			stateSlices[ballSlice] = 10 * stateMax;
			leftSlice = getLeftSliceNeighbour(ballSlice);
			rightSlice = getRightSliceNeighbour(ballSlice);
			this.stateSlices[leftSlice] += this.stateMax;
			this.stateSlices[rightSlice] += this.stateMax;
			this.stateMax *= 10;
		}
		for (int i = 0; i < this.stateSlices.length; i++) {
			this.stateSlices[i] /= this.stateMax;
			this.stateSlices[i] *= 10;
			if (this.stateSlices[i] < 0.1) {
				this.stateSlices[i] = 0.1;
			}
		}
	}

	private double calcBestViewDir(double bodyDir, double viewWidth,
			STATES state) {

		statistics.update(state);
		thisState = state;
		if (thisState != STATES.SEARCH_BALL)
			searchCount = 0;
		setUnseeableSlices(bodyDir, viewWidth);
		if (setPref) {
			for (int i = 0; i < preferredObjects.length; i++) {
				if (preferredObjects[i] != 0) {
					if (i < 22) {
						dummyPlayer.copy(world.getExpectedPlayers()[i]);
						dummyPlayer.extrapolate(1,
								dummyPlayer.getPConf().PLAYER_DECAY);
						objectPos.copy(dummyPlayer.getPosition());
					} else {
						// the ball
						dummyBall.copy(world.getBallRef());
						dummyBall.extrapolate(1, sConf.BALL_DECAY);
						objectPos.copy(dummyBall.getPosition());
					}
					int objectSlice = getSliceContainingPoint(world
							.getMyNextPosition(), objectPos);
					if (seeableSlices[objectSlice] == 0) {
						preferredObjects[i] = 0;
					}
				}
			}
		}
		// now preferred contains only seeable dynamic objects
		// set important object values

		boolean found = false;
		for (double pref : preferredObjects) {
			if (pref != 0) {
				found = true;
			}
		}
		setPref = found;

		if (!setPref) {
			Arrays.fill(importantObjectValue, 0);
		} else {
			Arrays.fill(importantObjectValue, -1);
			for (int i = 0; i < preferredObjects.length; i++) {
				if (preferredObjects[i] != 0) {
					if (i < 22) {
						dummyPlayer.copy(world.getExpectedPlayers()[i]);
						dummyPlayer.extrapolate(1,
								dummyPlayer.getPConf().PLAYER_DECAY);
						objectPos.copy(dummyPlayer.getPosition());
					} else {
						// the ball
						dummyBall.copy(world.getBallRef());
						dummyBall.extrapolate(1, sConf.BALL_DECAY);
						objectPos.copy(dummyBall.getPosition());
					}
					int objectSlice = getSliceContainingPoint(world
							.getMyNextPosition(), objectPos);
					double objectDist = world.getMyNextPosition().getDistance(
							objectPos);
					int importantSliceNum = 0;
					if (objectDist < 5) {
						importantSliceNum = 6;
					} else if (objectDist < 6) {
						importantSliceNum = 5;
					} else if (objectDist < 7) {
						importantSliceNum = 4;
					} else if (objectDist < 8) {
						importantSliceNum = 3;
					} else if (objectDist < 9) {
						importantSliceNum = 2;
					} else {
						importantSliceNum = 1;
					}

					int maxSliceDiff = (int) ((viewWidth / getSliceWidth()) / 2);
					if (maxSliceDiff < 1) {
						System.out
								.println("Error in NR --> no slices seeable???");
					}

					for (int j = 0; j < getNumberSlices(); j++) {
						int sliceDiff = getSliceDiff(j, objectSlice);
						double value = 0;
						if (sliceDiff > maxSliceDiff) {
							value = -1;
						} else if (sliceDiff > importantSliceNum) {
							value = preferredObjects[i];
						} else {
							value = preferredObjects[i]
									- (sliceDiff / importantSliceNum);
						}
						importantObjectValue[j] = Math.max(value,
								importantObjectValue[j]);
					}
				}
			}
		}

		if (state == STATES.PASS) {
			// watch ball and close players
			setDefaultValues(this.world.getMyPosition(), 4, 0.1, 1, 0.5, 0, 0,
					false, true, 2, 1, 1);
		} else if (state == STATES.DRIBBLE) {
			setDefaultValues(this.world.getMyPosition(), 6, 5, 5, 2, 0, 0,
					false, false, 4, 1, this.world.getMyPosition().y > 30 ? 4
							: 1);
			// setDribbleValues(world.getMyPosition());
		} else if (state == STATES.GOALKEEPER) {
			// watch only ball
			setDefaultValues(this.world.getMyPosition(), 0, 1, 1, 1, 1, 1,
					true, true, 1, 1, 1);
		} else if (state == STATES.GOALIE_KICK_IN) {
			// watch much field
			setDefaultValues(this.world.getMyPosition(), 0, 0.1, 5, 5, 1, 0,
					false, false, 4, 1, 0);
		} else if (state == STATES.INTERCEPT_BALL) {
			// watch primary to ball
			setDefaultValues(this.world.getMyPosition(), 100, 0.1, 3, 1, 0, 0,
					false, true, 1, 2, 1);
		} else if (state == STATES.SEARCH_BALL) {
			this.searchCount++;
			if (!(this.lastState == STATES.SEARCH_BALL))
				setSearchBallValues(this.world.getMyPosition());
		} else if (state == STATES.START_POSITION) {
			// watch ball
			// and players
			// close
			// players are
			// more
			// important
			// then far
			// away
			setDefaultValues(this.world.getMyPosition(), 4, 0.1, 2, 0.75, 0.25,
					0.1, false, true, 1, 1, 0.5);
		} else {
			// watch
			// ball
			// and
			// players
			// close
			// players
			// are
			// more
			// important
			// then
			// far
			// away
			setDefaultValues(this.world.getMyPosition(), 4, 0.1, 2, 0.75, 0.25,
					0.1, false, true, 1, 1, 1);
		}

		this.lastState = state;

		// number of relevant slices (slices that can be seen with given view
		// width)
		int slices = (int) (viewWidth / this.getSliceWidth());
		// at least one slice
		if (slices == 0)
			slices++;

		// init values

		// max Value for covered slices
		double maxValue = -1;
		double tempValue = 0;
		// middle of a slice
		double bestViewDirAbs = 0;
		double viewDirAbs = 0;
		int numberOfSlices = getNumberSlices();
		// get the sequence of slices with the max. sum value
		for (int i = 0; i < numberOfSlices; i++) {
			tempValue = 0;
			for (int j = 0; j < slices; j++) {
				tempValue += getFinalValue((i + j) % numberOfSlices, state);
			}
			if (tempValue > maxValue) {
				viewDirAbs = Vektor.normalize(getSliceLeftAngle(i)
						+ slices * getSliceWidth() / 2);
				if (Math.abs(Vektor.normalize(viewDirAbs - bodyDir)) <= 90) {
					maxValue = tempValue;
					bestViewDirAbs = viewDirAbs;
				}
			}
		}
		for (int i = 0; i < numberOfSlices; i++) {
			this.finalSlices[i] = getFinalValue(i, state);
			this.finalSlices[i] = Math.round(this.finalSlices[i] * 1000) / 1000.;
		}/*
			 * if (world.getNumber() == 6){ showValues(state,
			 * getSliceContainingAbsAngle(bestViewDirAbs)); }
			 */
		return bestViewDirAbs;
	}

	/**
	 * The best view direction (abs).
	 * 
	 * @param bodyDir -
	 *            players body direction
	 * @param viewWidth -
	 *            players view width
	 * @param state -
	 *            calling state
	 * @return - best head angle (abs)
	 */
	public double bestViewDirAbs(double bodyDir, double viewWidth, STATES state) {

		Arrays.fill(preferredObjects, 0);
		setPref = false;
		return calcBestViewDir(bodyDir, viewWidth, state);
	}

	/**
	 * The best view direction (abs).
	 * 
	 * @param bodyDir -
	 *            players body direction
	 * @param viewWidth -
	 *            players view width
	 * @param state -
	 *            calling state
	 * @param pref an size 23 array with values from -1 (not look at object) over 0 (look normal at object) to 1 (always look at object)
   * place 0-10 are own players, place 11 - 21 are opponent players, place 22 is the ball
	 * @return - best head angle (abs)
	 */
	public double bestViewDirAbs(double bodyDir, double viewWidth,
			STATES state, double[] pref) {

		preferredObjects = pref;
		setPref = true;
		return calcBestViewDir(bodyDir, viewWidth, state);
	}

	/**
	 * The best view direction (abs).
	 * 
	 * @param bodyDir -
	 *            players body direction
	 * @param viewWidth -
	 *            players view width
	 * @param state -
	 *            calling state
	 * @param pref the object to look at always
	 * @return - best head angle (abs)
	 */
	public double bestViewDirAbs(double bodyDir, double viewWidth,
			STATES state, DynamicObject pref) {

		Arrays.fill(preferredObjects, 0);
		if (pref instanceof Ball) {
			preferredObjects[22] = 1;
		} else {
			dummyPlayer.copy(((Player) pref));
			if (dummyPlayer.isFriend()) {
				preferredObjects[dummyPlayer.getNumber() - 1] = 1;
			} else {
				preferredObjects[dummyPlayer.getNumber() + 10] = 1;
			}
		}
		setPref = true;
		return calcBestViewDir(bodyDir, viewWidth, state);
	}

	private int getSliceDiff(int slice1, int slice2) {

		return Math.min(Math.abs(slice1 - slice2), this.getNumberSlices()
				- (Math.abs(slice1 - slice2)));
	}

	/**
	 * The value of a slice.
	 * 
	 * @param i -
	 *            slice index
	 * @param state -
	 *            the state calling
	 * @return value of slice i
	 * 
	 */
	private double getFinalValue(int i, STATES state) {

		switch (state) {
		case SEARCH_BALL: {
			double value = (Math.pow(roundHistory(sliceHistory[i]),
					historyWeight)
					* Math.pow(stateSlices[i], stateWeight) * Math.pow(
					estimatedSliceAreas[i] / (areaMax / 10), areaWeight));
			if (importantObjectValue[i] >= 0) {
				return (importantObjectValue[i] + (1 - importantObjectValue[i])
						* value);
			}
      return ((1 + importantObjectValue[i]) * value);
		}
		case DRIBBLE:
		default: {
			double value = (Math.pow(roundHistory(sliceHistory[i]),
					historyWeight)
					* Math.pow(stateSlices[i], stateWeight) * Math.pow(
					estimatedSliceAreas[i] / (areaMax / 10), areaWeight));
			if (importantObjectValue[i] >= 0) {
				return ((importantObjectValue[i] + (1 - importantObjectValue[i])
						* value) * seeableSlices[i]);
			}
      return ((1 + importantObjectValue[i]) * value * seeableSlices[i]);
		}
		}

	}

	/**
	 * show the state statistic.
	 * 
	 */
	public void showStatistics() {

		this.statistics.print();
	}

	/**
	 * gives the age of a point
	 * @param point the point
	 * @return the age
	 */
	public double getAgeOfPoint(Vektor point) {

		return this.sliceHistory[getSliceContainingPoint(this.world
				.getMyPosition(), point)];
	}

	/**
	 * returns the age of a direction.
	 * 
	 * @param angleAbs
	 * @return
	 * 
	 * TODO
	 * 
	 * fürs passen wäre es schöner, wenn auch das Alter benachbarter Slices
	 * mit eingehen würde (geht in die bewertungsfunktion ein) (rw)
	 */
	public double getAgeOfDir(double angleAbs) {

		return this.sliceHistory[getSliceContainingAbsAngle(angleAbs)];
	}

	/**
	 * For debugging.
	 * 
	 */
	public void showHistory() {

		System.out.print("History (slices=" + Math.pow(2, this.numberSlicesExp)
				+ ")");
		for (double d : this.sliceHistory) {
			System.out.print(":" + ((double) Math.round(d * 100) / 100));
		}
		System.out.println();
	}

	/**
	 * For debugging. Shows the NR-values depending on the state.
	 * 
	 * @param state -
	 *            state
	 * @param slice the slice
	 */
	public void showValues(STATES state, int slice) {

		System.out.println("Slice to view: " + slice);
		System.out.println("Cycle: " + this.world.getCycle());
		switch (state) {
		default:
			System.out.println("   actual State " + state.toString());
			System.out.println("Slices: History "
					+ Arrays.toString(this.sliceHistory));
			System.out.println("        Area    "
					+ Arrays.toString(this.estimatedSliceAreas));
			System.out.println("        Seeable "
					+ Arrays.toString(this.seeableSlices));
			System.out.println("        State   "
					+ Arrays.toString(this.stateSlices));
			System.out.println("        Final   "
					+ Arrays.toString(this.finalSlices));
			System.out.println();
		}
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see robocup.component.worldmodel.IModel#update(robocup.component.infotypes.RefereeInfo)
	 */
	public void update(RefereeInfo info) {

		// TODO Auto-generated method stub

	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see robocup.component.worldmodel.IModel#update(robocup.component.infotypes.PlayModeInfo)
	 */
	public void update(PlayModeInfo info) {

		// TODO Auto-generated method stub

	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see robocup.component.worldmodel.IModel#update(robocup.component.infotypes.PlayerSayInfo)
	 */
	public void update(PlayerSayInfo info) {

		// TODO Auto-generated method stub

	}
}
